﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Core.Reader;
using Core.Writer;
using Core.Tools;
using Core.Entities;
using Core.Enums;

namespace Core.Formats
{
    public class XmlFormat : IDocumentFormat
    {
        private static readonly char[] NameEndChars = { ' ', '\n', '\r', '\t', '>', '/' };
        private static readonly char[] AttributeEndChars = { '=', '/', '>' };
        private static readonly char[] TextEndChars = { '"', '\'' };

        private string InternalNodeBefore = "";
        private string InternalAttributeBefore = "A.";
        private string InternalNodeAfter = "";
        private string InternalAttributeAfter = "";
        private string InternalContentName = "Content";
        private string InternalArrayNodeName = "item";

        public string NodeBefore
        {
            get
            {
                return InternalNodeBefore;
            }
            set
            {
                if (value == null)
                {
                    InternalNodeBefore = "";
                }
                else
                {
                    InternalNodeBefore = value;
                }
            }
        }
        public string AttributeBefore
        {
            get
            {
                return InternalAttributeBefore;
            }
            set
            {
                if (value == null)
                {
                    InternalAttributeBefore = "";
                }
                else
                {
                    InternalAttributeBefore = value;
                }
            }
        }
        public string NodeAfter
        {
            get
            {
                return InternalNodeAfter;
            }
            set
            {
                if (value == null)
                {
                    InternalNodeAfter = "";
                }
                else
                {
                    InternalNodeAfter = value;
                }
            }
        }
        public string AttributeAfter
        {
            get
            {
                return InternalAttributeAfter;
            }
            set
            {
                if (value == null)
                {
                    InternalAttributeAfter = "";
                }
                else
                {
                    InternalAttributeAfter = value;
                }
            }
        }
        public string ContentName
        {
            get
            {
                return InternalContentName;
            }
            set
            {
                if (value == null)
                {
                    throw new NullReferenceException("ContentName");
                }

                InternalContentName = value;
            }
        }

        public string ArrayNodeName
        {
            get
            {
                return InternalArrayNodeName;
            }
            set
            {
                InternalArrayNodeName = value;
            }
        }

        public unsafe void Decode(string Text, IDataWriter Writer)
        {
            fixed (char* LpText = Text)
            {
                object Value;

                Decode(LpText, 0, Text.Length, (CCWInfo) =>
                {
                    Writer.Next();

                    return Writer;
                }, out Value);
            }
        }

        private unsafe string ReadName(char* LpText, int Begin, int End)
        {
            int EndIndex = StringHelper.IndexOf(LpText, NameEndChars, Begin, End);

            if (EndIndex == -1)
            {
                throw new FormatException("XML");
            }

            return StringHelper.Substring(LpText, Begin, EndIndex);
        }

        private unsafe int ReadAttributes(char* LpText, int Begin, int End, Func<CreateChildrenWriterInfo, IDataWriter> CreateWriter)
        {
            IDataWriter Writer = null;

        Loop:

            int Flag = StringHelper.IndexOf(LpText, AttributeEndChars, Begin, End);

            if (Flag == -1)
            {
                goto ErrorFormat;
            }

            if (LpText[Flag] != '=')
            {
                return Flag;
            }

            if (Writer == null)
            {
                var CCWInfo = new CreateChildrenWriterInfo();

                Writer = CreateWriter(CCWInfo);
            }

            string AttributeName = StringHelper.Trim(LpText, Begin, Flag);

            Begin = StringHelper.IndexOf(LpText, TextEndChars, Flag, End);

            if (Begin == -1)
            {
                goto ErrorFormat;
            }

            Flag = StringHelper.IndexOf(LpText, LpText[Begin], Begin + 1, End);

            if (Flag == -1)
            {
                goto ErrorFormat;
            }

            string AttributeValue = StringHelper.Substring(LpText, Begin + 1, Flag);

            Writer.Add(AttributeBefore + AttributeName + AttributeAfter, ParseValue(AttributeValue));

            Begin = Flag + 1;

            goto Loop;

        ErrorFormat:
            throw new FormatException("XML");
        }

        private unsafe int Decode(char* LpText, int Begin, int End, Func<CreateChildrenWriterInfo, IDataWriter> CreateWriter, out object Value)
        {
            Value = null;

            IDataWriter Writer = null;

        DocodeNode:

            int Flag = StringHelper.IndexOf(LpText, '<', Begin, End);

            /* 文档结尾 */
            if (Flag == -1)
            {
                Flag = End;

                goto DecodeText;
            }
            /* 匹配到 </ */
            else if (LpText[Flag + 1] == '/')
            {
                End = StringHelper.IndexOf(LpText, '>', Flag + 1, End);

                if (End == -1)
                {
                    throw new FormatException("XML");
                }

                ++End;

                goto DecodeText;
            }

            /* 读取到新节点 */

            ++Flag;

            /* 解析到 CDATA 块 */
            if (StringHelper.Equals(LpText, Flag, End, "![CDATA["))
            {
                Flag += 8;

                Begin = StringHelper.IndexOf(LpText, "]]>", Flag, End);

                string StringContent = StringHelper.Trim(LpText, Flag, Begin);

                Value = ParseValue(StringContent);

                Begin += 3;

                goto DocodeNode;
            }

            if (Writer == null)
            {
                var CCWInfo = new CreateChildrenWriterInfo();

                Writer = CreateWriter(CCWInfo);
            }

            /* 读取节点名称 <Name */
            string NodeName = ReadName(LpText, Flag, End);

            Flag += NodeName.Length;

            NodeName = NodeBefore + NodeName + NodeAfter;

            IDataWriter ChildrenWriter = null;

            Func<CreateChildrenWriterInfo, IDataWriter> CreateChildrenWriter = (CCWInfo) =>
            {
                if (ChildrenWriter != null)
                {
                    return ChildrenWriter;
                }

                CCWInfo.Name = NodeName;

                ChildrenWriter = Writer.CreateChildrenWriter(CCWInfo);

                ChildrenWriter.Next();

                return ChildrenWriter;
            };

            Flag = ReadAttributes(LpText, Flag, End, CreateChildrenWriter);

            if (Flag >= End)
            {
                throw new FormatException("XML");
            }

            if (LpText[Flag] == '/')
            {
                Begin = Flag + 2;

                goto DocodeNode;
            }

            ++Flag;

            object NodeValue;

            /* 解析子节点中的子元素 */
            Begin = Decode(LpText, Flag, End, CreateChildrenWriter, out NodeValue);

            /* 设置子节点的信息 */
            if (ChildrenWriter == null)
            {
                if (NodeValue != null)
                {
                    Writer.Add(NodeName, NodeValue);
                }
            }
            else
            {
                if (NodeValue != null)
                {
                    ChildrenWriter.Add("Content", NodeValue);
                }

                Writer.Add(NodeName, ChildrenWriter);
            }

            goto DocodeNode;

        DecodeText:

            if (Value == null)
            {
                string StringContent = StringHelper.Trim(LpText, Begin, Flag);

                Value = ParseValue(StringContent);
            }

            return End;
        }

        public void Encode(IDataReader Reader, TextWriter Writer)
        {
            using (var AttributesWriter = new StringWriter())
            {
                EncodeItems(Reader, Writer, AttributesWriter);
            }
        }

        private void EncodeItems(IDataReader Reader, TextWriter NodesWriter, TextWriter AttributesWriter)
        {
            switch (Reader.ObjectType)
            {
                case RWTypes.Table:
                case RWTypes.Object:
                    goto EncodeObject;
                case RWTypes.Array:
                    goto EncodeArray;
                default:
                    throw new Exception("Invalid Reader!");
            }

        EncodeObject:
            while (Reader.Read())
            {
                int Index = 0;

                foreach (var Item in Reader.Fields)
                {
                    var Value = Reader[Index];

                    if (Value is IDataReader)
                    {
                        NodesWriter.Write("<");
                        NodesWriter.Write(Item);

                        string C_Nodes;


                        using (var C_NodesBuilder = new StringWriter())
                        {
                            EncodeItems((IDataReader)Value, C_NodesBuilder, NodesWriter);

                            C_Nodes = C_NodesBuilder.ToString();
                        }

                        if (!string.IsNullOrEmpty(C_Nodes))
                        {
                            NodesWriter.Write(">");
                            NodesWriter.Write(C_Nodes);
                            NodesWriter.Write("</");
                            NodesWriter.Write(Item);
                            NodesWriter.Write(">");
                        }
                        else
                        {
                            NodesWriter.Write("/>");
                        }
                    }
                    else if (Item.StartsWith(NodeBefore) && Item.EndsWith(NodeAfter))
                    {
                        NodesWriter.Write("<");
                        NodesWriter.Write(Item);
                        NodesWriter.Write(">");
                        NodesWriter.Write(GetString(Value));
                        NodesWriter.Write("</");
                        NodesWriter.Write(Item);
                        NodesWriter.Write(">");
                    }
                    else if (Item.StartsWith(AttributeBefore) && Item.EndsWith(AttributeAfter))
                    {
                        AttributesWriter.Write(Item);
                        AttributesWriter.Write("=");
                        AttributesWriter.Write("\"");
                        NodesWriter.Write(GetString(Value));
                        AttributesWriter.Write("\"");
                    }

                    ++Index;
                }
            }
            return;

        EncodeArray:
            if (Reader.Read())
            {
                for (int i = 0; i < Reader.Count; ++i)
                {
                    var Value = Reader[i];

                    if (Value is IDataReader)
                    {
                        NodesWriter.Write("<");
                        NodesWriter.Write(ArrayNodeName);

                        var C_NodesBuilder = new StringWriter();

                        EncodeItems((IDataReader)Value, C_NodesBuilder, NodesWriter);

                        var C_Nodes = C_NodesBuilder.ToString();

                        if (!string.IsNullOrEmpty(C_Nodes))
                        {
                            NodesWriter.Write(">");
                            NodesWriter.Write(C_Nodes);
                            NodesWriter.Write("</");
                            NodesWriter.Write(ArrayNodeName);
                            NodesWriter.Write(">");
                        }
                        else
                        {
                            NodesWriter.Write("/>");
                        }
                    }
                    else
                    {
                        NodesWriter.Write("<");
                        NodesWriter.Write(ArrayNodeName);
                        NodesWriter.Write(">");
                        NodesWriter.Write(Value);
                        NodesWriter.Write("</");
                        NodesWriter.Write(ArrayNodeName);
                        NodesWriter.Write(">");
                    }
                }
            }
        }

        public string GetString(object Value)
        {
            if (Value == null)
            {
                return "";
            }

            return Value.ToString();
        }

        public object ParseValue(string Text)
        {
            if (string.IsNullOrEmpty(Text))
            {
                return null;
            }

            return Text;
        }
    }
}